<!DOCTYPE html>
<html lang="zh_hans">
    <!-- title -->




<!-- keywords -->




<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" >
    <meta name="author" content="Apm29">
    <meta name="renderer" content="webkit">
    <meta name="copyright" content="Apm29">
    
    <meta name="keywords" content="hexo,hexo-theme,hexo-blog">
    
    <meta name="description" content="daily post">
    <meta http-equiv="Cache-control" content="no-cache">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
    <title>用Flutter重构应用 · 生活中的Design</title>
    <style type="text/css">
    @font-face {
        font-family: 'Oswald-Regular';
        src: url("/font/Oswald-Regular.ttf");
    }

    body {
        margin: 0;
    }

    header,
    footer,
    .back-top,
    .sidebar,
    .container,
    .site-intro-meta,
    .toc-wrapper {
        display: none;
    }

    .site-intro {
        position: relative;
        z-index: 3;
        width: 100%;
        /* height: 50vh; */
        overflow: hidden;
    }

    .site-intro-placeholder {
        position: absolute;
        z-index: -2;
        top: 0;
        left: 0;
        width: calc(100% + 300px);
        height: 100%;
        background: repeating-linear-gradient(-45deg, #444 0, #444 80px, #333 80px, #333 160px);
        background-position: center center;
        transform: translate3d(-226px, 0, 0);
        animation: gradient-move 2.5s ease-out 0s infinite;
    }

    @keyframes gradient-move {
        0% {
            transform: translate3d(-226px, 0, 0);
        }
        100% {
            transform: translate3d(0, 0, 0);
        }
    }

</style>

    <link rel="preload" href= "/css/style.css?v=20180824" as="style" onload="this.onload=null;this.rel='stylesheet'" />
    <link rel="stylesheet" href= "/css/mobile.css?v=20180824" media="(max-width: 980px)">
    
    <link rel="preload" href="https://cdnjs.cloudflare.com/ajax/libs/fancybox/3.2.5/jquery.fancybox.min.css" as="style" onload="this.onload=null;this.rel='stylesheet'" />
    
    <!-- /*! loadCSS. [c]2017 Filament Group, Inc. MIT License */
/* This file is meant as a standalone workflow for
- testing support for link[rel=preload]
- enabling async CSS loading in browsers that do not support rel=preload
- applying rel preload css once loaded, whether supported or not.
*/ -->
<script>
(function( w ){
	"use strict";
	// rel=preload support test
	if( !w.loadCSS ){
		w.loadCSS = function(){};
	}
	// define on the loadCSS obj
	var rp = loadCSS.relpreload = {};
	// rel=preload feature support test
	// runs once and returns a function for compat purposes
	rp.support = (function(){
		var ret;
		try {
			ret = w.document.createElement( "link" ).relList.supports( "preload" );
		} catch (e) {
			ret = false;
		}
		return function(){
			return ret;
		};
	})();

	// if preload isn't supported, get an asynchronous load by using a non-matching media attribute
	// then change that media back to its intended value on load
	rp.bindMediaToggle = function( link ){
		// remember existing media attr for ultimate state, or default to 'all'
		var finalMedia = link.media || "all";

		function enableStylesheet(){
			link.media = finalMedia;
		}

		// bind load handlers to enable media
		if( link.addEventListener ){
			link.addEventListener( "load", enableStylesheet );
		} else if( link.attachEvent ){
			link.attachEvent( "onload", enableStylesheet );
		}

		// Set rel and non-applicable media type to start an async request
		// note: timeout allows this to happen async to let rendering continue in IE
		setTimeout(function(){
			link.rel = "stylesheet";
			link.media = "only x";
		});
		// also enable media after 3 seconds,
		// which will catch very old browsers (android 2.x, old firefox) that don't support onload on link
		setTimeout( enableStylesheet, 3000 );
	};

	// loop through link elements in DOM
	rp.poly = function(){
		// double check this to prevent external calls from running
		if( rp.support() ){
			return;
		}
		var links = w.document.getElementsByTagName( "link" );
		for( var i = 0; i < links.length; i++ ){
			var link = links[ i ];
			// qualify links to those with rel=preload and as=style attrs
			if( link.rel === "preload" && link.getAttribute( "as" ) === "style" && !link.getAttribute( "data-loadcss" ) ){
				// prevent rerunning on link
				link.setAttribute( "data-loadcss", true );
				// bind listeners to toggle media back
				rp.bindMediaToggle( link );
			}
		}
	};

	// if unsupported, run the polyfill
	if( !rp.support() ){
		// run once at least
		rp.poly();

		// rerun poly on an interval until onload
		var run = w.setInterval( rp.poly, 500 );
		if( w.addEventListener ){
			w.addEventListener( "load", function(){
				rp.poly();
				w.clearInterval( run );
			} );
		} else if( w.attachEvent ){
			w.attachEvent( "onload", function(){
				rp.poly();
				w.clearInterval( run );
			} );
		}
	}


	// commonjs
	if( typeof exports !== "undefined" ){
		exports.loadCSS = loadCSS;
	}
	else {
		w.loadCSS = loadCSS;
	}
}( typeof global !== "undefined" ? global : this ) );
</script>

    <link rel="icon" href= "/assets/favicon.ico" />
    <link rel="preload" href="https://unpkg.com/webfontloader@1.6.28/webfontloader.js" as="script" />
    <link rel="preload" href="https://unpkg.com/jquery@3.3.1/dist/jquery.min.js" as="script" />
    <link rel="preload" href="/scripts/main.js" as="script" />
    <link rel="preload" as="font" href="/font/Oswald-Regular.ttf" crossorigin>
    <link rel="preload" as="font" href="https://at.alicdn.com/t/font_327081_1dta1rlogw17zaor.woff" crossorigin>
    
    <!-- fancybox -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/fancybox/3.2.5/jquery.fancybox.min.js" defer></script>
    <!-- 百度统计  -->
    
    <!-- 谷歌统计  -->
    
<meta name="generator" content="Hexo 5.4.2"></head>

    
        <body class="post-body">
    
    
<header class="header">

    <div class="read-progress"></div>
    <div class="header-sidebar-menu">&#xe775;</div>
    <!-- post页的toggle banner  -->
    
    <div class="banner">
            <div class="blog-title">
                <a href="/" >生活中的Design.</a>
            </div>
            <div class="post-title">
                <a href="#" class="post-name">用Flutter重构应用</a>
            </div>
    </div>
    
    <a class="home-link" href=/>生活中的Design.</a>
</header>
    <div class="wrapper">
        <div class="site-intro" style="







height:50vh;
">
    
    <!-- 主页  -->
    
    
    <!-- 404页  -->
            
    <div class="site-intro-placeholder"></div>
    <div class="site-intro-img" style="background-image: url(/intro/post-bg.jpg)"></div>
    <div class="site-intro-meta">
        <!-- 标题  -->
        <h1 class="intro-title">
            <!-- 主页  -->
            
            用Flutter重构应用
            <!-- 404 -->
            
        </h1>
        <!-- 副标题 -->
        <p class="intro-subtitle">
            <!-- 主页副标题  -->
            
            
            <!-- 404 -->
            
        </p>
        <!-- 文章页meta -->
        
            <div class="post-intros">
                <!-- 文章页标签  -->
                
                    <div class= post-intro-tags >
    
        <a class="post-tag" href="javascript:void(0);" data-tags = "Android">Android</a>
    
        <a class="post-tag" href="javascript:void(0);" data-tags = "Flutter">Flutter</a>
    
        <a class="post-tag" href="javascript:void(0);" data-tags = "Dart">Dart</a>
    
        <a class="post-tag" href="javascript:void(0);" data-tags = "Kotlin">Kotlin</a>
    
        <a class="post-tag" href="javascript:void(0);" data-tags = "Gradle">Gradle</a>
    
</div>
                
                
                    <div class="post-intro-read">
                        <span>字数统计: <span class="post-count word-count">5.6k</span>阅读时长: <span class="post-count reading-time">26 min</span></span>
                    </div>
                
                <div class="post-intro-meta">
                    <span class="post-intro-calander iconfont-archer">&#xe676;</span>
                    <span class="post-intro-time">2019/12/23</span>
                    
                    <span id="busuanzi_container_page_pv" class="busuanzi-pv">
                        <span class="iconfont-archer">&#xe602;</span>
                        <span id="busuanzi_value_page_pv"></span>
                    </span>
                    
                    <span class="shareWrapper">
                        <span class="iconfont-archer shareIcon">&#xe71d;</span>
                        <span class="shareText">Share</span>
                        <ul class="shareList">
                            <li class="iconfont-archer share-qr" data-type="qr">&#xe75b;
                                <div class="share-qrcode"></div>
                            </li>
                            <li class="iconfont-archer" data-type="weibo">&#xe619;</li>
                            <li class="iconfont-archer" data-type="qzone">&#xe62e;</li>
                            <li class="iconfont-archer" data-type="twitter">&#xe634;</li>
                            <li class="iconfont-archer" data-type="facebook">&#xe67a;</li>
                        </ul>
                    </span>
                </div>
            </div>
        
    </div>
</div>
        <script>

  // get user agent
  var browser = {
    versions: function () {
      var u = window.navigator.userAgent;
      return {
        userAgent: u,
        trident: u.indexOf('Trident') > -1, //IE内核
        presto: u.indexOf('Presto') > -1, //opera内核
        webKit: u.indexOf('AppleWebKit') > -1, //苹果、谷歌内核
        gecko: u.indexOf('Gecko') > -1 && u.indexOf('KHTML') == -1, //火狐内核
        mobile: !!u.match(/AppleWebKit.*Mobile.*/), //是否为移动终端
        ios: !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/), //ios终端
        android: u.indexOf('Android') > -1 || u.indexOf('Linux') > -1, //android终端或者uc浏览器
        iPhone: u.indexOf('iPhone') > -1 || u.indexOf('Mac') > -1, //是否为iPhone或者安卓QQ浏览器
        iPad: u.indexOf('iPad') > -1, //是否为iPad
        webApp: u.indexOf('Safari') == -1, //是否为web应用程序，没有头部与底部
        weixin: u.indexOf('MicroMessenger') == -1, //是否为微信浏览器
        uc: u.indexOf('UCBrowser') > -1 //是否为android下的UC浏览器
      };
    }()
  }
  console.log("userAgent:" + browser.versions.userAgent);

  // callback
  function fontLoaded() {
    console.log('font loaded');
    if (document.getElementsByClassName('site-intro-meta')) {
      document.getElementsByClassName('intro-title')[0].classList.add('intro-fade-in');
      document.getElementsByClassName('intro-subtitle')[0].classList.add('intro-fade-in');
      var postIntros = document.getElementsByClassName('post-intros')[0]
      if (postIntros) {
        postIntros.classList.add('post-fade-in');
      }
    }
  }

  // UC不支持跨域，所以直接显示
  function asyncCb() {
    if (browser.versions.uc) {
      console.log("UCBrowser");
      fontLoaded();
    } else {
      WebFont.load({
        custom: {
          families: ['Oswald-Regular']
        },
        loading: function () {  //所有字体开始加载
          // console.log('loading');
        },
        active: function () {  //所有字体已渲染
          fontLoaded();
        },
        inactive: function () { //字体预加载失败，无效字体或浏览器不支持加载
          console.log('inactive: timeout');
          fontLoaded();
        },
        timeout: 5000 // Set the timeout to two seconds
      });
    }
  }

  function asyncErr() {
    console.warn('script load from CDN failed, will load local script')
  }

  // load webfont-loader async, and add callback function
  function async(u, cb, err) {
    var d = document, t = 'script',
      o = d.createElement(t),
      s = d.getElementsByTagName(t)[0];
    o.src = u;
    if (cb) { o.addEventListener('load', function (e) { cb(null, e); }, false); }
    if (err) { o.addEventListener('error', function (e) { err(null, e); }, false); }
    s.parentNode.insertBefore(o, s);
  }

  var asyncLoadWithFallBack = function (arr, success, reject) {
    var currReject = function () {
      reject()
      arr.shift()
      if (arr.length)
        async(arr[0], success, currReject)
    }

    async(arr[0], success, currReject)
  }

  asyncLoadWithFallBack([
    "https://unpkg.com/webfontloader@1.6.28/webfontloader.js",
    "https://cdn.bootcss.com/webfont/1.6.28/webfontloader.js",
    "/lib/webfontloader.min.js"
  ], asyncCb, asyncErr)
</script>
        
        <img class="loading" src="/assets/loading.svg" style="display: block; margin: 6rem auto 0 auto; width: 6rem; height: 6rem;" />
        <div class="container container-unloaded">
            <main class="main post-page">
    <article class="article-entry">
        <h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><h1 id="开发环境"><a href="#开发环境" class="headerlink" title="开发环境"></a>开发环境</h1><ul>
<li>macOS Catalina10.15.2</li>
<li>AndroidStudio 3.5.3</li>
<li>Xcode 11.3</li>
<li>看看doctor<br><code>$ flutter doctor -v</code><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line">[✓] Flutter (Channel master, v1.13.6-pre.16, on Mac OS X 10.15.2 19C57, locale zh-Hans-CN)</span><br><span class="line">    • Flutter version 1.13.6-pre.16 at /Users/ciih/flutter</span><br><span class="line">    • Framework revision fcaf9c4070 (34 hours ago), 2019-12-21 14:03:01 -0800</span><br><span class="line">    • Engine revision 33813929e3</span><br><span class="line">    • Dart version 2.8.0 (build 2.8.0-dev.0.0 886615d0f9)</span><br><span class="line"></span><br><span class="line"> </span><br><span class="line">[✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3)</span><br><span class="line">    • Android SDK at /Users/ciih/Library/Android/sdk</span><br><span class="line">    • Android NDK location not configured (optional; useful for native profiling support)</span><br><span class="line">    • Platform android-29, build-tools 28.0.3</span><br><span class="line">    • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java</span><br><span class="line">    • Java version OpenJDK Runtime Environment (build 1.8.0_202-release-1483-b49-5587405)</span><br><span class="line">    • All Android licenses accepted.</span><br><span class="line"></span><br><span class="line">[✓] Xcode - develop for iOS and macOS (Xcode 11.3)</span><br><span class="line">    • Xcode at /Applications/Xcode.app/Contents/Developer</span><br><span class="line">    • Xcode 11.3, Build version 11C29</span><br><span class="line">    • CocoaPods version 1.9.0.beta.2</span><br><span class="line"></span><br><span class="line">[✓] Chrome - develop for the web</span><br><span class="line">    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome</span><br><span class="line"></span><br><span class="line">[✓] Android Studio (version 3.5)</span><br><span class="line">    • Android Studio at /Applications/Android Studio.app/Contents</span><br><span class="line">    • Flutter plugin version 42.1.1</span><br><span class="line">    • Dart plugin version 191.8593</span><br><span class="line">    • Java version OpenJDK Runtime Environment (build 1.8.0_202-release-1483-b49-5587405)</span><br><span class="line"></span><br><span class="line">[✓] IntelliJ IDEA Community Edition (version 2019.1.3)</span><br><span class="line">    • IntelliJ at /Applications/IntelliJ IDEA CE.app</span><br><span class="line">    • Flutter plugin version 39.0.3</span><br><span class="line">    • Dart plugin version 191.8593</span><br><span class="line"></span><br><span class="line">[✓] VS Code (version 1.39.2)</span><br><span class="line">    • VS Code at /Applications/Visual Studio Code.app/Contents</span><br><span class="line">    • Flutter extension version 3.6.0</span><br><span class="line"></span><br><span class="line">[✓] Connected device (3 available)</span><br><span class="line">    • 钱水利的 iPhone • 46e7963e6f0f38edf5c1c78f2f782a814f1edb34 • ios            • iOS 13.3</span><br><span class="line">    • Chrome      • chrome                                   • web-javascript • Google Chrome 79.0.3945.88</span><br><span class="line">    • Web Server  • web-server                               • web-javascript • Flutter Tools</span><br><span class="line"></span><br><span class="line">• No issues found!</span><br></pre></td></tr></table></figure>



</li>
</ul>
<h1 id="项目创建"><a href="#项目创建" class="headerlink" title="项目创建"></a>项目创建</h1><blockquote>
<p>为了省事直接用AndroidStudio创建了</p>
</blockquote>
<h2 id="命令行创建项目"><a href="#命令行创建项目" class="headerlink" title="命令行创建项目"></a>命令行创建项目</h2><ul>
<li><p>简单创建</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ flutter create [项目名称]</span><br></pre></td></tr></table></figure></li>
<li><p><code>-t [模板名称]</code>可以指定对应的模板,现在可以创建<code>app</code>–应用 <code>module</code>–app的模块 <code>package</code>–纯Dart代码项目 <code>plugin</code>–flutter插件项目 </p>
</li>
<li><p><code>-i [swift/objc]</code> 设置iOS开发语言,swift或者oc</p>
</li>
<li><p><code>-a [kotlin/java]</code> 设置Android开发语言</p>
</li>
<li><p><code>--[no-]androidx</code> 指定要不要使用Androidx支持库</p>
</li>
</ul>
<h2 id="设置子模块"><a href="#设置子模块" class="headerlink" title="设置子模块"></a>设置子模块</h2><p>运行<code>flutter create --project-name [你的项目名称] -i swift -a kotlin --androidx</code> 之后项目根目录大概是这个样子滴</p>
<ul>
<li><strong>android</strong> android端代码</li>
<li><strong>ios</strong> ios端代码</li>
<li><strong>web</strong> web端代码</li>
<li><strong>test</strong> 测试代码</li>
<li><strong>lib</strong> 存放Dart代码</li>
<li><strong>pubspec.lock</strong> </li>
<li><strong>pubspec.yaml</strong> 依赖文件,类似于Android的gradle或者Vue.js里面的package.json</li>
</ul>
<p>完成之后需要将项目纳入版本控制,但是ios/web/android三个文件夹需要作为子模块(git submodule)<br>首先在github或者其他git网站新建子模块的repo(空的仓库),然后分别进入ios/web/android目录,<br><code>$ git init</code> 初始化本地git仓库<br><code>$ git remote add origin [你的子模块仓库地址]</code> 设置远程仓库地址<br><code>$ git commit -ma &quot;init&quot;</code> 提交所有文件到本地仓库<br><code>$ git push -u origin master</code> 推送本地代码到远程仓库</p>
<p>然后在git网站新建你的总repo,在根目录下初始化git仓库<br>然后添加子模块<code>git submodule add [子模块仓库链接] [名称]</code> ,注意名称要改成ios/android/web等,和本地目录名称对应起来,然后也是提交<br><code>$ git remote add origin [你的仓库地址]</code><br><code>$ git commit -ma &quot;init&quot;</code><br><code>$ git push -u --recurse-submodules=on-demand
 origin master</code> 推送本地代码到远程仓库<br>不出意外的话你的git仓库看起来是这样的<br><img src="/images/2019/12/23/48c205d0-255e-11ea-a317-cbe164904c80.png" alt="image.png"></p>
<blockquote>
<p>子模块设置完成,如果需要更新子模块/拉取服务端改动等,可以参考git子模块那篇文章</p>
</blockquote>
<h1 id="项目框架搭建"><a href="#项目框架搭建" class="headerlink" title="项目框架搭建"></a>项目框架搭建</h1><p>从状态管理,路由管理,视图组件,动画,网络组件,本地存储,硬件交互等方面搭建新项目,尽量选择侵入性小的库</p>
<h2 id="状态管理"><a href="#状态管理" class="headerlink" title="状态管理"></a>状态管理</h2><p>之前的项目在状态管理方面用过<code>BLoC</code>和<code>Provider</code>,新的项目想试一试<code>flutter_redux</code>,<code>fish_redux</code>看了下太复杂了,小项目可能不太合适,有种大炮打蚊子的感觉…</p>
<h3 id="flutter-redux-基本概念"><a href="#flutter-redux-基本概念" class="headerlink" title="flutter_redux 基本概念"></a>flutter_redux 基本概念</h3><p>redux的基本概念:</p>
<ul>
<li>store 状态管理</li>
<li>state 应用/数据状态</li>
<li>action view发出的动作</li>
<li>reducer 一个接收state,action返回新的state的函数</li>
<li>middleware 在action和reducer中间执行</li>
</ul>
<h3 id="redux的Store"><a href="#redux的Store" class="headerlink" title="redux的Store"></a>redux的Store</h3><p><code>flutter_redux</code>依赖于<code>redux</code>包,所以我们也要先了解了<code>redux</code>里面的概念,<code>Store</code>是整个App的状态Owner,<strong>唯一能改变<code>Store</code>中状态树的方法就是通过<code>Store</code>发送(<code>dispatch</code>)一个<code>Action</code></strong>,action会先通过<code>middleware</code>,如果没有被<code>middleware</code>中断,则会传递至<code>reducer</code>,<code>reducer</code>根据<code>action</code>改变<code>store</code>中的<code>state</code>完成<code>state</code>树的更新,当然具体逻辑可能更复杂一些. </p>
<pre><code>- 定义action: `final increment = &apos;INCREMENT&apos;;`
- 定义reducer: `int counterReducer(int state, action) =&gt; state+1` 只可以根据action来决定如何改变state,我这里为了简单就直接写个加一了
- 创建Store: `Store&lt;int&gt;(counterReducer,initialState:0)`
- 分发一个action: `store.dispatch(increment);` action可以是任意类型
- 然后store中的state(原来是0)就会改变</code></pre><p><strong>store提供了两种方式获得state实例:state属性 或者 onChange提供的Stream</strong></p>
<p>看看构造方法全都明白了:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">Store(</span><br><span class="line">    this.reducer, &#123;</span><br><span class="line">    State initialState,</span><br><span class="line">    //middleware集合</span><br><span class="line">    List&lt;Middleware&lt;State&gt;&gt; middleware = const [],</span><br><span class="line">    //是否使用同步的Stream控制器(一般就false)</span><br><span class="line">    bool syncStream = false,</span><br><span class="line"></span><br><span class="line">    /// 若设置为true,在原来State和reducer处理过的State相同的情况下Store不会发送事件</span><br><span class="line">    bool distinct = false,</span><br><span class="line">  &#125;) : _changeController = StreamController.broadcast(sync: syncStream) &#123;</span><br><span class="line">    _state = initialState;</span><br><span class="line">    //创建分发器</span><br><span class="line">    _dispatchers = _createDispatchers(</span><br><span class="line">      middleware,</span><br><span class="line">      _createReduceAndNotify(distinct),</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure>
<p>_createDispatchers: 创建一个NextDispatcher集合,放入middleware集合转换来的NextDispatcher,再放入_createReduceAndNotify转化来的NextDispatcher,这个集合就是dispatch方法的调用栈,dispatch方法会先调用第一个NextDispatcher</p>
<ul>
<li>再看看Middleware的定义<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">typedef dynamic Middleware&lt;State&gt;(</span><br><span class="line">  Store&lt;State&gt; store,</span><br><span class="line">  dynamic action,</span><br><span class="line">  NextDispatcher next,</span><br><span class="line">);</span><br></pre></td></tr></table></figure>
<blockquote>
<p>这里的NextDispatcher是对reducer和middleware的简单包装,让他们接受action并返回action(或者被拦截…所以定义里面返回值是dynamic)</p>
</blockquote>
</li>
</ul>
<p>因为middleware都在栈顶,所以先调用middleware转换成的NextDispatcher,这个next就是下一个middleware或者reducer对应的NextDispatcher,若你在middleware中调用了next,这个调用链就会一直调用到最后知道reducer调用完成,完成对state的修改.(这个是不是叫责任链设计模式?)</p>
<blockquote>
<p>来个图 middleware–&gt; middleware(我可以中断也可以往下传球) —&gt; …. —&gt;reducer</p>
</blockquote>
<p>reducer完成了拼图的最后一块,最后的NextDispatcher,往StreamController(<code>_changeController</code>)add了一个修改后的state.看到这里恍然大悟,原来又是你:Stream,所以很好猜,下面章节要讲的Widget里用于局部更新的StoreConnector/StoreBuilder应该就是StreamBuilder包装下的东西.</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">NextDispatcher _createReduceAndNotify(bool distinct) &#123;</span><br><span class="line">   return (dynamic action) &#123;</span><br><span class="line">     final state = reducer(_state, action);</span><br><span class="line"></span><br><span class="line">     if (distinct &amp;&amp; state == _state) return;</span><br><span class="line"></span><br><span class="line">     _state = state;</span><br><span class="line">     _changeController.add(state);</span><br><span class="line">   &#125;;</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure>

<p>flutter_redux的组件:</p>
<ul>
<li>StoreProvider store的提供者,类似于Proivder中各种Provider的概念</li>
<li>StoreConnector 类似于Provider中Consumer,响应变化.包含两个@require的函数:<ul>
<li>converter,用于将state转化为view_model</li>
<li>builder,view_model转为view(widget)</li>
<li>onInit:初始化操作</li>
</ul>
</li>
</ul>
<h3 id="StoreProvider"><a href="#StoreProvider" class="headerlink" title="StoreProvider"></a>StoreProvider</h3><p>继承自InheritedWidget,用于给所有子不见提供Store,里面有用的就一个Store对象,和其他状态管理组件例如Provider一样,有个静态方法<code>static Store&lt;S&gt; of&lt;S&gt;(BuildContext,&#123;bool listen=true&#125;)</code>来获取实例. 几乎一模一样的参数.</p>
<ul>
<li><p>当该方法传入<code>listen = true</code>时<br>  通过<code>inheritFromWidgetOfExactType</code>(这个方法在1.12已经被标记为deprecated,新的api是<code>dependOnInheritedWidgetOfExactType</code>,名字与意义更符合一点)获取provider,api文档说的很明显,会把调用者context注册,当该Type对应的InheritedWidget发生改变的时候将会rebuild该context对应的组件</p>
</li>
<li><p>当<code>listen = false</code>时<br>  通过<code>ancestorInheritedElementForWidgetOfExactType</code>(这个方法在1.12已经被标记为deprecated,新的api是<code>getElementForInheritedWidgetOfExactType</code>,)获取provider,文档说的是获取给定的Type最近的<code>InheritedElement</code>,然后再通过<code>InheritedElement</code>拿到<code>InheritedWidget</code>,也就是我们需要的provider.</p>
</li>
<li><p>源码大概结构</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line">class StoreProvider&lt;S&gt; extends InheritedWidget &#123;</span><br><span class="line">  final Store&lt;S&gt; _store;</span><br><span class="line"></span><br><span class="line">  /// 通过Store和Child创建</span><br><span class="line">  const StoreProvider(&#123;</span><br><span class="line">    Key key,</span><br><span class="line">    @required Store&lt;S&gt; store,</span><br><span class="line">    @required Widget child,</span><br><span class="line">  &#125;)  : assert(store != null),</span><br><span class="line">        assert(child != null),</span><br><span class="line">        _store = store,</span><br><span class="line">        super(key: key, child: child);</span><br><span class="line"></span><br><span class="line">  ///用于获取Store实例,在initState中使用时要传listen = false</span><br><span class="line">  static Store&lt;S&gt; of&lt;S&gt;(BuildContext context, &#123;bool listen = true&#125;) &#123;</span><br><span class="line">    final type = _typeOf&lt;StoreProvider&lt;S&gt;&gt;();</span><br><span class="line">    final provider = (listen</span><br><span class="line">        ? context.inheritFromWidgetOfExactType(type)</span><br><span class="line">        : context</span><br><span class="line">            .ancestorInheritedElementForWidgetOfExactType(type)</span><br><span class="line">            ?.widget) as StoreProvider&lt;S&gt;;</span><br><span class="line"></span><br><span class="line">    if (provider == null) throw StoreProviderError(type);</span><br><span class="line"></span><br><span class="line">    return provider._store;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  // Dart中获取类型的方法,很多地方会用到</span><br><span class="line">  static Type _typeOf&lt;T&gt;() =&gt; T;</span><br><span class="line"></span><br><span class="line">  ///是否需要通知子Widget的判断方法</span><br><span class="line">  @override</span><br><span class="line">  bool updateShouldNotify(StoreProvider&lt;S&gt; oldWidget) =&gt;</span><br><span class="line">      _store != oldWidget._store;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

</li>
</ul>
<h3 id="StoreConnector"><a href="#StoreConnector" class="headerlink" title="StoreConnector"></a>StoreConnector</h3><p>这个类似于Provider中的Consumer,<code>class StoreConnector&lt;S, ViewModel&gt; extends StatelessWidget &#123;&#125;</code>,泛型S表示State类型,ViewModel表示从State类型转化来的model数据,用于生成Widget.这个组件实际只是给已给名叫<code>__StoreStreamListener</code>的StatefulWidget包装了下.我们先看看它的State干了什么能监听Store内的State变化.</p>
<p>1.两个重要的属性</p>
<ul>
<li><strong>Stream<ViewModel> stream;</strong></li>
<li><strong>ViewModel latestValue;</strong></li>
</ul>
<p>2.initState方法</p>
<ul>
<li>调用StoreConnector的onInit</li>
<li>在下一帧调用StoreConnector的onInitialBuild</li>
<li>通过StoreConnector的converter吧store转换成viewModel赋值给 latestValue</li>
<li>通过StoreConnector.store.onChange属性获得流(Stream),经过一系列操作转换成ViewModel流赋值给属性<code>stream</code><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">stream = widget.store.onChange</span><br><span class="line">       .where(_ignoreChange)</span><br><span class="line">       .map(_mapConverter)</span><br><span class="line">       // Don&#x27;t use `Stream.distinct` because it cannot capture the initial</span><br><span class="line">       // ViewModel produced by the `converter`.</span><br><span class="line">       .where(_whereDistinct)</span><br><span class="line">       // After each ViewModel is emitted from the Stream, we update the</span><br><span class="line">       // latestValue. Important: This must be done after all other optional</span><br><span class="line">       // transformations, such as ignoreChange.</span><br><span class="line">       .transform(StreamTransformer.fromHandlers(handleData: _handleChange));</span><br><span class="line"></span><br><span class="line">    void _handleChange(ViewModel vm, EventSink&lt;ViewModel&gt; sink) &#123;</span><br><span class="line">   	if (widget.onWillChange != null) &#123;</span><br><span class="line">     	widget.onWillChange(latestValue, vm);</span><br><span class="line">   	&#125;</span><br><span class="line"></span><br><span class="line">   	latestValue = vm;</span><br><span class="line"></span><br><span class="line">   	if (widget.onDidChange != null) &#123;</span><br><span class="line">     WidgetsBinding.instance.addPostFrameCallback((_) &#123;</span><br><span class="line">       	widget.onDidChange(latestValue);</span><br><span class="line">     	&#125;);</span><br><span class="line">   &#125;</span><br><span class="line"></span><br><span class="line">   sink.add(vm);</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure>
<blockquote>
<p>这里有几个Stream操作符:where是筛选,map是映射,transform 方法就是把一个 Stream 作为输入，然后经过计算或数据转换，输出为另一个 Stream。另一个 Stream 中的数据类型可以不同于原类型，数据多少也可以不同</p>
</blockquote>
</li>
</ul>
<p>所以其他方法都并不重要了,_createStream方法以及定义好了流中数据转换和过滤的方式,其他方法都是一些生命周期回调方便定制其他功能,最后看看build方法做了些什么事:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">@override</span><br><span class="line">  Widget build(BuildContext context) &#123;</span><br><span class="line">    return widget.rebuildOnChange</span><br><span class="line">        ? StreamBuilder&lt;ViewModel&gt;(</span><br><span class="line">            stream: stream,</span><br><span class="line">            builder: (context, snapshot) =&gt; widget.builder(</span><br><span class="line">              context,</span><br><span class="line">              latestValue,</span><br><span class="line">            ),</span><br><span class="line">          )</span><br><span class="line">        : widget.builder(context, latestValue);</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure>

<p>没错!!!包装了下StreamBuilder,通过它监听Stream中的新数据,返回builder创建的新Widget.</p>
<h3 id="StoreBuilder"><a href="#StoreBuilder" class="headerlink" title="StoreBuilder"></a>StoreBuilder</h3><p>其实和StoreConnector差不多,懒人版本的StoreConnector,少了个converter,state–&gt;viewModel这部分就省了,直接build完事.其他功能都一毛一样.</p>
<h3 id="Redux包下提供的工具方法"><a href="#Redux包下提供的工具方法" class="headerlink" title="Redux包下提供的工具方法"></a>Redux包下提供的工具方法</h3><p>redux包提供了一些工具类工具方法,让我免去大量switch..case模板代码</p>
<p>比如说我们先定义state和action</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">class AppState &#123;</span><br><span class="line">  final List&lt;Item&gt; items;</span><br><span class="line">  AppState(this.items);</span><br><span class="line">&#125;</span><br><span class="line">class LoadItemsAction &#123;&#125;</span><br><span class="line">class UpdateItemsAction &#123;&#125;</span><br><span class="line">class AddItemAction&#123;&#125;</span><br><span class="line">class RemoveItemAction &#123;&#125;</span><br><span class="line">class ShuffleItemsAction &#123;&#125;</span><br><span class="line">class ReverseItemsAction &#123;&#125;</span><br><span class="line">class ItemsLoadedAction&lt;Item&gt; &#123;</span><br><span class="line">  final List&lt;Item&gt; items;</span><br><span class="line">  ItemsLoadedAction(this.items);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>我们现在有一个state保存一个item集合,有加载,更新,添加,删除等许多action,如果是之前的代码我们可能要写好多switch..case代码,像下面这样👇:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">final appReducer = (AppState state, action) &#123;</span><br><span class="line">  if (action is ItemsLoadedAction) &#123;</span><br><span class="line">    return new AppState(action.items);</span><br><span class="line">  &#125; else if (action is UpdateItemsAction) &#123;</span><br><span class="line">    return ...;</span><br><span class="line">  &#125; else if (action is AddItemAction) &#123;</span><br><span class="line">    return ...;</span><br><span class="line">  &#125; else if (action is RemoveItemAction) &#123;</span><br><span class="line">    return ...;</span><br><span class="line">  &#125; else if (action is ShuffleItemsAction) &#123;</span><br><span class="line">    return ...;</span><br><span class="line">  &#125; else if (action is ReverseItemsAction) &#123;</span><br><span class="line">    return ...;</span><br><span class="line">  &#125; else &#123;</span><br><span class="line">    return state;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>redux提供了combineReducer方法和TypedReducer类来将多个小的reducer组合为一个reducer:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">final removeItemReducer = (AppState state, RemoveItemAction action) &#123;</span><br><span class="line">  return ...;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">///....</span><br><span class="line"></span><br><span class="line">final Reducer&lt;AppState&gt; appReducer = combineReducers([</span><br><span class="line">  new TypedReducer&lt;AppState, LoadTodosAction&gt;(loadItemsReducer),</span><br><span class="line">  new TypedReducer&lt;AppState, UpdateItemsAction&gt;(updateItemsReducer),</span><br><span class="line">  new TypedReducer&lt;AppState, AddItemAction&gt;(addItemReducer),</span><br><span class="line">  new TypedReducer&lt;AppState, RemoveItemAction&gt;(removeItemReducer),</span><br><span class="line">  new TypedReducer&lt;AppState, ShuffleItemAction&gt;(shuffleItemsReducer),</span><br><span class="line">  new TypedReducer&lt;AppState, ReverseItemAction&gt;(reverseItemsReducer),</span><br><span class="line">]);</span><br></pre></td></tr></table></figure>
<p>middleware当然也有:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">final List&lt;Middleware&lt;AppState&gt;&gt; middleware = [</span><br><span class="line">  new TypedMiddleware&lt;AppState, LoadTodosAction&gt;(loadItemsMiddleware),</span><br><span class="line">  new TypedMiddleware&lt;AppState, AddTodoAction&gt;(saveItemsMiddleware),</span><br><span class="line">  new TypedMiddleware&lt;AppState, ClearCompletedAction&gt;(saveItemsMiddleware),</span><br><span class="line">  new TypedMiddleware&lt;AppState, ToggleAllAction&gt;(saveItemsMiddleware),</span><br><span class="line">  new TypedMiddleware&lt;AppState, UpdateTodoAction&gt;(saveItemsMiddleware),</span><br><span class="line">  new TypedMiddleware&lt;AppState, TodosLoadedAction&gt;(saveItemsMiddleware),</span><br><span class="line">];</span><br></pre></td></tr></table></figure>
<h2 id="路由管理"><a href="#路由管理" class="headerlink" title="路由管理"></a>路由管理</h2><p>路由管理使用fluro,其实flutter框架自带的路由已经很好了,不过fluro可以往路由路径里加入参数,这个有些项目比较方便,可以自己取舍.</p>
<h3 id="引入"><a href="#引入" class="headerlink" title="引入"></a>引入</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">dependencies:</span><br><span class="line">  fluro: ^1.5.1</span><br></pre></td></tr></table></figure>

<h3 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h3><p>提供一个全局Router实例</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">import &#x27;package:fluro/fluro.dart&#x27;;</span><br><span class="line"></span><br><span class="line">class Application&#123;</span><br><span class="line"> </span><br><span class="line">  static Router router;</span><br><span class="line"></span><br><span class="line">  static void init() &#123;</span><br><span class="line">    Router router = Router();</span><br><span class="line">    Routes.configureRoutes(router);</span><br><span class="line">    Application.router = router;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>定义一个路由映射类</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">class Routes &#123;</span><br><span class="line">  static String root = &quot;/&quot;;</span><br><span class="line">  static String home = &quot;/home&quot;;</span><br><span class="line">  static String login = &quot;/login&quot;;</span><br><span class="line">  static String personalSettings = &quot;/personal_settings&quot;;</span><br><span class="line"></span><br><span class="line">  static void configureRoutes(Router router) &#123;</span><br><span class="line">    router.notFoundHandler = new Handler(handlerFunc: (</span><br><span class="line">      BuildContext context,</span><br><span class="line">      Map&lt;String, List&lt;String&gt;&gt; params,</span><br><span class="line">    ) &#123;</span><br><span class="line">      print(&quot;ROUTE WAS NOT FOUND !!!&quot;);</span><br><span class="line">      return NotFoundPage();</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    /// 第一个参数是路由地址，第二个参数是页面跳转和传参，第三个参数是默认的转场动画，可以看上图</span><br><span class="line">    router.define(root, handler: splashHandler);</span><br><span class="line">    router.define(home, handler: homeHandler);</span><br><span class="line">    router.define(login, handler: loginHandler);</span><br><span class="line">    router.define(personalSettings, handler: personalSettingsHandler);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>可以看到我设置了四个路径,<code>&#39;/&#39;</code>为默认第一个页面,<code>configureRoutes</code>手动在main方法中调用来初始化Router,我给他设置了一个404页面,然后定义了各个路由的handler:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line">class NotFoundPage extends StatelessWidget &#123;</span><br><span class="line">  @override</span><br><span class="line">  Widget build(BuildContext context) &#123;</span><br><span class="line">    return Scaffold(</span><br><span class="line">      body: Center(</span><br><span class="line">        child: Text(&#x27;404 路由未找到&#x27;),</span><br><span class="line">      ),</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">/// 跳转到首页Splash</span><br><span class="line">final splashHandler = new Handler(</span><br><span class="line">  handlerFunc: (BuildContext context, Map&lt;String, List&lt;String&gt;&gt; params) &#123;</span><br><span class="line">    return SplashPage();</span><br><span class="line">  &#125;,</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line">final homeHandler = new Handler(</span><br><span class="line">  handlerFunc: (BuildContext context, Map&lt;String, List&lt;String&gt;&gt; params) &#123;</span><br><span class="line">    return HomePage();</span><br><span class="line">  &#125;,</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line">final personalSettingsHandler = new Handler(</span><br><span class="line">  handlerFunc: (BuildContext context, Map&lt;String, List&lt;String&gt;&gt; params) &#123;</span><br><span class="line">    return PersonalSettingsPage();</span><br><span class="line">  &#125;,</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line">final loginHandler = new Handler(</span><br><span class="line">  handlerFunc: (BuildContext context, Map&lt;String, List&lt;String&gt;&gt; params) &#123;</span><br><span class="line">    return LoginPage();</span><br><span class="line">  &#125;,</span><br><span class="line">);</span><br></pre></td></tr></table></figure>

<p>然后在main.dart中先调用下init:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">Future&lt;void&gt; main() async &#123;</span><br><span class="line">  WidgetsFlutterBinding.ensureInitialized();</span><br><span class="line">  // 初始化缓存,这个是自己包装的SharedPreferences工具</span><br><span class="line">  await Cache().init();</span><br><span class="line">  // 注册 fluro routes</span><br><span class="line">  Application.init();</span><br><span class="line">  runApp(FlutterReduxApp());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>然后就可以在其他地方使用了,当然你也可以再封装个路由跳转类:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Application.router.navigateTo(</span><br><span class="line">      context,</span><br><span class="line">      Routes.personalSettings,</span><br><span class="line">      transition: TransitionType.inFromBottom,</span><br><span class="line">    );</span><br></pre></td></tr></table></figure>

<p>路由跳转类:(这边基本没用到参数…只支持String参数并且不能有中文),fluro可以定义转场动画,需要的可以去看看Github:<a target="_blank" rel="noopener" href="https://github.com/theyakka/fluro">fluro</a></p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">class AppRouter &#123;</span><br><span class="line">  static Future toHomeAndReplaceSelf(BuildContext context) &#123;</span><br><span class="line">    return Application.router.navigateTo(context, Routes.home, replace: true);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  static Future toPersonal(BuildContext context) &#123;</span><br><span class="line">    return Application.router.navigateTo(</span><br><span class="line">      context,</span><br><span class="line">      Routes.personalSettings,</span><br><span class="line">      transition: TransitionType.inFromBottom,</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  static Future toHome(BuildContext context, ActiveTab activeTab) &#123;</span><br><span class="line">    store.dispatch(TabSwitchAction(activeTab.index, context));</span><br><span class="line">    return Application.router</span><br><span class="line">        .navigateTo(context, Routes.home, replace: true, clearStack: true);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  static Future toLogin(BuildContext context) &#123;</span><br><span class="line">    return Application.router.navigateTo(</span><br><span class="line">      context,</span><br><span class="line">      Routes.login,</span><br><span class="line">      transition</span><br><span class="line">          : TransitionType.inFromBottom,</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>定义参数是这样的:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">var usersHandler = Handler(handlerFunc: (BuildContext context, Map&lt;String, dynamic&gt; params) &#123;</span><br><span class="line">  return UsersScreen(params[&quot;id&quot;][0]);</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">void defineRoutes(Router router) &#123;</span><br><span class="line">  router.define(&quot;/users/:id&quot;, handler: usersHandler);</span><br><span class="line"></span><br><span class="line">  // it is also possible to define the route transition to use</span><br><span class="line">  // router.define(&quot;users/:id&quot;, handler: usersHandler, transitionType: TransitionType.inFromLeft);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="国际化"><a href="#国际化" class="headerlink" title="国际化"></a>国际化</h2><p>如果您的应用可能会给另一种语言的用户使用，那么您需要“国际化”它。这意味着您在编写应用程序时需要为应用程序支持的每种语言环境， 设置“本地化”的一些值，如文本和布局。Flutter提供一些widgets和类，以帮助实现国际化，而Flutter的库本身也是国际化的。<br>参考:<a target="_blank" rel="noopener" href="https://flutterchina.club/tutorials/internationalization/">Flutter中文网</a>,<a target="_blank" rel="noopener" href="https://juejin.im/post/5c701379f265da2d9b5e196a">Flutter-国际化适配</a></p>
<h3 id="导入flutter-localization包"><a href="#导入flutter-localization包" class="headerlink" title="导入flutter_localization包"></a>导入flutter_localization包</h3><p>pubspec.yml文件:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">dependencies:</span><br><span class="line">  flutter:</span><br><span class="line">    sdk: flutter</span><br><span class="line">  flutter_localizations:</span><br><span class="line">    sdk: flutter</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<h3 id="引入-1"><a href="#引入-1" class="headerlink" title="引入"></a>引入</h3><p>指定<code>MaterialApp</code>的<code>localizationsDelegates</code>和<code>supportedLocales</code>：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">import &#x27;package:flutter_localizations/flutter_localizations.dart&#x27;;</span><br><span class="line"></span><br><span class="line">new MaterialApp(</span><br><span class="line"> localizationsDelegates: [</span><br><span class="line">   // ... app-specific localization delegate[s] here</span><br><span class="line">   GlobalMaterialLocalizations.delegate,</span><br><span class="line">   GlobalWidgetsLocalizations.delegate,</span><br><span class="line"> ],</span><br><span class="line"> supportedLocales: [</span><br><span class="line">    const Locale(&#x27;en&#x27;, &#x27;US&#x27;), // English</span><br><span class="line">    const Locale(&#x27;he&#x27;, &#x27;IL&#x27;), // Hebrew</span><br><span class="line">    // ... other locales the app supports</span><br><span class="line">  ],</span><br><span class="line">  // ...</span><br><span class="line">)</span><br></pre></td></tr></table></figure>

<h3 id="使用Flutter-i18n插件"><a href="#使用Flutter-i18n插件" class="headerlink" title="使用Flutter-i18n插件"></a>使用Flutter-i18n插件</h3><p>如果你已经成功安装插件，打开项目后，会发现自动添加以下两个文件：</p>
<p>lib/generated/i18n.dart 主要的国际化文件，主要使用的类为S<br>res/values/string_en.arb 该文件主要适配英文语言，内容为json格式<br>修改:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">MaterialApp(</span><br><span class="line">      title: &#x27;Flutter Demo&#x27;,</span><br><span class="line">      theme: ThemeData(</span><br><span class="line">        primarySwatch: Colors.red,</span><br><span class="line">      ),</span><br><span class="line">      localizationsDelegates: const [</span><br><span class="line">        S.delegate,</span><br><span class="line">        GlobalMaterialLocalizations.delegate,</span><br><span class="line">        GlobalWidgetsLocalizations.delegate</span><br><span class="line">      ],</span><br><span class="line">      supportedLocales: S.delegate.supportedLocales,</span><br><span class="line">    );</span><br></pre></td></tr></table></figure>

<ul>
<li>localizationsDelegates本地化委托参数</li>
<li>S.delegate 我们项目的本地化委托类,这个你不用管,他会根据你的arb文件自动生成对应的函数<br>GlobalMaterialLocalizations.delegate和GlobalWidgetsLocalizations.delegate为</li>
<li>flutter_localizations插件包提供的委托，如果你使用MaterialApp这个部件的- - GlobalMaterialLocalizations.delegate这个可以不用</li>
<li>supportedLocales支持的本地化</li>
<li>S.delegate.supportedLocales我们项目支持的本地化，这个你不用管，它会在你添加arb文件时自动更新你的支持的本地化</li>
</ul>
<h3 id="声明资源"><a href="#声明资源" class="headerlink" title="声明资源"></a>声明资源</h3><p>.arb文件</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">	&quot;title&quot;:&#x27;AppName&#x27;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>占位符:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">	&quot;successMessage&quot;:&quot;操作成功:$action&quot;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>列表: 支持语法为：key+zero/one/two/few/many/other</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  &quot;selectZero&quot;:&quot;没有了&quot;,</span><br><span class="line">  &quot;selectOne&quot;:&quot;一个&quot;,</span><br><span class="line">  &quot;selectTwo&quot;:&quot;两个&quot;,</span><br><span class="line">  &quot;selectFew&quot;:&quot;一些&quot;,</span><br><span class="line">  &quot;selectMany&quot;:&quot;很多&quot;,</span><br><span class="line">  &quot;selectOther&quot;:&quot;其它&quot;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="使用资源"><a href="#使用资源" class="headerlink" title="使用资源"></a>使用资源</h3><ul>
<li>普通:<code>S.of(context).title</code></li>
<li>带占位符:<code>S.of(context).successMessage(&quot;登录&quot;)</code></li>
<li>列表<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">S.of(context).select(0);//零个</span><br><span class="line">S.of(context).select(1);//一个</span><br><span class="line">S.of(context).select(&quot;many&quot;);//多个</span><br><span class="line">S.of(context).select(null);//其它</span><br></pre></td></tr></table></figure>



</li>
</ul>
<h1 id="插件的使用"><a href="#插件的使用" class="headerlink" title="插件的使用"></a>插件的使用</h1><h2 id="camera-0-5-7-2"><a href="#camera-0-5-7-2" class="headerlink" title="camera 0.5.7+2"></a>camera 0.5.7+2</h2><h3 id="引入-2"><a href="#引入-2" class="headerlink" title="引入"></a>引入</h3><p>pubspec.yml:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">camera: ^0.5.7+2</span><br></pre></td></tr></table></figure>

<h3 id="iOS权限"><a href="#iOS权限" class="headerlink" title="iOS权限"></a>iOS权限</h3><p>info.plist</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&lt;key&gt;NSCameraUsageDescription&lt;/key&gt;</span><br><span class="line">&lt;string&gt;APP需要您的同意，才能访问相机进行拍照，如禁止将无法拍照更新信息&lt;/string&gt;</span><br><span class="line">&lt;key&gt;NSMicrophoneUsageDescription&lt;/key&gt;</span><br><span class="line">&lt;string&gt;APP需要您的同意，才能访问麦克风进行录音，如禁止将无法录音发送语音信息&lt;/string&gt;</span><br></pre></td></tr></table></figure>

<h3 id="获取Camera"><a href="#获取Camera" class="headerlink" title="获取Camera"></a>获取Camera</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">List&lt;CameraDescription&gt; cameras = await availableCameras();</span><br></pre></td></tr></table></figure>

<h3 id="使用-1"><a href="#使用-1" class="headerlink" title="使用"></a>使用</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line">class CameraApp extends StatefulWidget &#123;</span><br><span class="line">  @override</span><br><span class="line">  _CameraAppState createState() =&gt; _CameraAppState();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">class _CameraAppState extends State&lt;CameraApp&gt; &#123;</span><br><span class="line">  CameraController controller;</span><br><span class="line"></span><br><span class="line">  @override</span><br><span class="line">  void initState() &#123;</span><br><span class="line">    super.initState();</span><br><span class="line">    controller = CameraController(cameras[0], ResolutionPreset.medium);</span><br><span class="line">    controller.initialize().then((_) &#123;</span><br><span class="line">      if (!mounted) &#123;</span><br><span class="line">        return;</span><br><span class="line">      &#125;</span><br><span class="line">      setState(() &#123;&#125;);</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  @override</span><br><span class="line">  void dispose() &#123;</span><br><span class="line">    controller?.dispose();</span><br><span class="line">    super.dispose();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  @override</span><br><span class="line">  Widget build(BuildContext context) &#123;</span><br><span class="line">    if (!controller.value.isInitialized) &#123;</span><br><span class="line">      return Container();</span><br><span class="line">    &#125;</span><br><span class="line">    return AspectRatio(</span><br><span class="line">        aspectRatio:</span><br><span class="line">        controller.value.aspectRatio,</span><br><span class="line">        child: CameraPreview(controller));</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h1 id="Widget使用"><a href="#Widget使用" class="headerlink" title="Widget使用"></a>Widget使用</h1><h2 id="ClipPath"><a href="#ClipPath" class="headerlink" title="ClipPath"></a>ClipPath</h2><p>ClipPath传入一个Clipper,和Child,Clipper将会用getClip返回的path来裁剪Child</p>
<p>示例:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br></pre></td><td class="code"><pre><span class="line">import &#x27;package:axj_app/generated/i18n.dart&#x27;;</span><br><span class="line">import &#x27;package:flutter/cupertino.dart&#x27;;</span><br><span class="line">import &#x27;package:flutter/material.dart&#x27;;</span><br><span class="line">import &#x27;package:camera/camera.dart&#x27;;</span><br><span class="line"></span><br><span class="line">List&lt;CameraDescription&gt; cameras;</span><br><span class="line"></span><br><span class="line">class AuthPage extends StatefulWidget &#123;</span><br><span class="line">  @override</span><br><span class="line">  _AuthPageState createState() =&gt; _AuthPageState();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">class _AuthPageState extends State&lt;AuthPage&gt; &#123;</span><br><span class="line">  @override</span><br><span class="line">  Widget build(BuildContext context) &#123;</span><br><span class="line">    return Scaffold(</span><br><span class="line">      appBar: AppBar(</span><br><span class="line">        title: Text(S.of(context).authTitle),</span><br><span class="line">        actions: &lt;Widget&gt;[</span><br><span class="line">          IconButton(</span><br><span class="line">              icon: Icon(Icons.switch_camera),</span><br><span class="line">              onPressed: () &#123;</span><br><span class="line">                switchCamera();</span><br><span class="line">              &#125;)</span><br><span class="line">        ],</span><br><span class="line">      ),</span><br><span class="line">      body: cameraReady &amp;&amp; controllerReady</span><br><span class="line">          ? Stack(</span><br><span class="line">              children: &lt;Widget&gt;[</span><br><span class="line">                Align(</span><br><span class="line">                  alignment: Alignment.center,</span><br><span class="line">                  child: ClipPath(</span><br><span class="line">                    clipBehavior: Clip.antiAlias,</span><br><span class="line">                    clipper: FaceClipper(),</span><br><span class="line">                    child: AspectRatio(</span><br><span class="line">                      aspectRatio: controller.value.aspectRatio,</span><br><span class="line">                      child: CameraPreview(controller),</span><br><span class="line">                    ),</span><br><span class="line">                  ),</span><br><span class="line">                )</span><br><span class="line">              ],</span><br><span class="line">            )</span><br><span class="line">          : Center(</span><br><span class="line">              child: Column(</span><br><span class="line">                mainAxisSize: MainAxisSize.min,</span><br><span class="line">                children: &lt;Widget&gt;[</span><br><span class="line">                  CupertinoActivityIndicator(),</span><br><span class="line">                  SizedBox(height: 32),</span><br><span class="line">                  Text(S.of(context).loadingCameraHint)</span><br><span class="line">                ],</span><br><span class="line">              ),</span><br><span class="line">            ),</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  CameraController controller;</span><br><span class="line"></span><br><span class="line">  bool get cameraReady =&gt; cameras != null &amp;&amp; cameras.isNotEmpty;</span><br><span class="line"></span><br><span class="line">  bool get controllerReady =&gt; controller?.value?.isInitialized ?? false;</span><br><span class="line"></span><br><span class="line">  int currentCameraIndex = 1;</span><br><span class="line"></span><br><span class="line">  @override</span><br><span class="line">  void initState() &#123;</span><br><span class="line">    () async &#123;</span><br><span class="line">      if (!cameraReady) &#123;</span><br><span class="line">        cameras = await availableCameras();</span><br><span class="line">      &#125;</span><br><span class="line">      await initializeCameraController();</span><br><span class="line">    &#125;();</span><br><span class="line">    super.initState();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  @override</span><br><span class="line">  void dispose() &#123;</span><br><span class="line">    controller?.dispose();</span><br><span class="line">    super.dispose();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  void switchCamera() &#123;</span><br><span class="line">    if (!cameraReady) &#123;</span><br><span class="line">      return;</span><br><span class="line">    &#125;</span><br><span class="line">    currentCameraIndex += 1;</span><br><span class="line">    if (currentCameraIndex == cameras.length) &#123;</span><br><span class="line">      currentCameraIndex = 0;</span><br><span class="line">    &#125;</span><br><span class="line">    initializeCameraController();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  Future initializeCameraController() async &#123;</span><br><span class="line">    setState(() &#123;&#125;);</span><br><span class="line">    controller =</span><br><span class="line">        CameraController(cameras[currentCameraIndex], ResolutionPreset.high);</span><br><span class="line">    await controller.initialize();</span><br><span class="line">    await Future.delayed(Duration(milliseconds: 400));</span><br><span class="line">    if (!mounted) &#123;</span><br><span class="line">      return;</span><br><span class="line">    &#125;</span><br><span class="line">    setState(() &#123;&#125;);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">class FaceClipper extends CustomClipper&lt;Path&gt; &#123;</span><br><span class="line">  @override</span><br><span class="line">  Path getClip(Size size) &#123;</span><br><span class="line">    var path = Path();</span><br><span class="line"></span><br><span class="line">    Rect faceRect =</span><br><span class="line">        Rect.fromLTWH(20, 20, size.width - 40, (size.width - 40) * 1.3);</span><br><span class="line"></span><br><span class="line">    path.moveTo(faceRect.topCenter.dx, faceRect.topCenter.dy);</span><br><span class="line"></span><br><span class="line">    Offset p1 = faceRect.topRight.translate(0, 0);</span><br><span class="line">    Offset p2 = faceRect.centerRight.translate(-10, 0);</span><br><span class="line"></span><br><span class="line">    path.quadraticBezierTo(p1.dx, p1.dy, p2.dx, p2.dy);</span><br><span class="line"></span><br><span class="line">    p1 = faceRect.bottomRight.translate(-30, -25);</span><br><span class="line">    p2 = faceRect.bottomCenter.translate(10, 0);</span><br><span class="line"></span><br><span class="line">    path.quadraticBezierTo(p1.dx, p1.dy, p2.dx, p2.dy);</span><br><span class="line">    p2 = faceRect.bottomCenter.translate(-10, 0);</span><br><span class="line">    path.lineTo(p2.dx, p2.dy);</span><br><span class="line"></span><br><span class="line">    p1 = faceRect.bottomLeft.translate(30, -25);</span><br><span class="line">    p2 = faceRect.centerLeft.translate(10, 0);</span><br><span class="line"></span><br><span class="line">    path.quadraticBezierTo(p1.dx, p1.dy, p2.dx, p2.dy);</span><br><span class="line"></span><br><span class="line">    p1 = faceRect.topLeft.translate(0, 0);</span><br><span class="line">    p2 = faceRect.topCenter.translate(0, 0);</span><br><span class="line"></span><br><span class="line">    path.quadraticBezierTo(p1.dx, p1.dy, p2.dx, p2.dy);</span><br><span class="line"></span><br><span class="line">    var rightEarRect = Rect.fromLTWH(</span><br><span class="line">      faceRect.centerRight.dx - 20,</span><br><span class="line">      faceRect.centerRight.dy - 35,</span><br><span class="line">      20,</span><br><span class="line">      60,</span><br><span class="line">    );</span><br><span class="line">    path.addArc(rightEarRect, -pi / 2, pi);</span><br><span class="line"></span><br><span class="line">    path.addPolygon([</span><br><span class="line">      rightEarRect.bottomCenter,</span><br><span class="line">      rightEarRect.bottomLeft,</span><br><span class="line">      rightEarRect.topCenter,</span><br><span class="line">    ], true);</span><br><span class="line"></span><br><span class="line">    var leftEarRect = Rect.fromLTWH(</span><br><span class="line">          faceRect.centerLeft.dx,</span><br><span class="line">          faceRect.centerLeft.dy - 35,</span><br><span class="line">          20,</span><br><span class="line">          60,</span><br><span class="line">        );</span><br><span class="line">    path.addArc(</span><br><span class="line">        leftEarRect,</span><br><span class="line">        pi / 2,</span><br><span class="line">        pi);</span><br><span class="line"></span><br><span class="line">    path.addPolygon([</span><br><span class="line">      leftEarRect.topCenter,</span><br><span class="line">      leftEarRect.bottomRight,</span><br><span class="line">      leftEarRect.bottomCenter,</span><br><span class="line">    ], true);</span><br><span class="line"></span><br><span class="line">    path.close();</span><br><span class="line">    return path;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  @override</span><br><span class="line">  bool shouldReclip(CustomClipper oldClipper) &#123;</span><br><span class="line">    return false;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<p>用ClipPath和自定义的CustomClipper裁剪了个人脸(大概吧.都是凑出来的)</p>
<hr>
<h2 id="简单封装"><a href="#简单封装" class="headerlink" title="简单封装"></a>简单封装</h2><h3 id="Dio简单封装"><a href="#Dio简单封装" class="headerlink" title="Dio简单封装"></a>Dio简单封装</h3><p>之前的项目使用过Dio作为网络请求的框架,使用起来比较友好,因为是FlutterChina维护的项目,看文档和提issue都比较方便. 现在最新的版本是3.0,比我刚接触的时候成熟了不少,已经支持Multiple-part File,Post二进制流数据等功能.</p>
<p>我们先搞一个单例,方便全局修改dio的配置</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">class Api &#123;</span><br><span class="line">  Api._() &#123;</span><br><span class="line">    init();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  static bool printLog = true;</span><br><span class="line">  static Api _instance;</span><br><span class="line"></span><br><span class="line">  static Api getInstance() &#123;</span><br><span class="line">    if (_instance == null) &#123;</span><br><span class="line">      _instance = Api._();</span><br><span class="line">    &#125;</span><br><span class="line">    return _instance;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  factory Api() &#123;</span><br><span class="line">    return getInstance();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  Dio _dio;</span><br><span class="line"></span><br><span class="line">  void init() &#123;</span><br><span class="line">    _dio = Dio(BaseOptions(</span><br><span class="line">      method: &quot;POST&quot;,</span><br><span class="line">      connectTimeout: 10000,</span><br><span class="line">      receiveTimeout: 20000,</span><br><span class="line">      baseUrl: BaseUrl,</span><br><span class="line">    ));</span><br><span class="line">   _dio.interceptors.add(DioLogInterceptor());</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>如图,隐藏主构造函数,提供一个没名字的工厂方法,这样获取实例的方式被简化为<code>Api()</code></p>
<p><code>_init</code>方法只执行一次,初始化Dio实例,给他配好默认方法,超时参数,还有baseUrl,然后加入了一个日志拦截器(自己写的,比较简单),方便Debug,</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">_dio = Dio(BaseOptions(</span><br><span class="line">      method: &quot;POST&quot;,</span><br><span class="line">      connectTimeout: 10000,</span><br><span class="line">      receiveTimeout: 20000,</span><br><span class="line">      baseUrl: BaseUrl,</span><br><span class="line">    ));</span><br><span class="line">   _dio.interceptors.add(DioLogInterceptor());</span><br></pre></td></tr></table></figure>

<p>再定义一个请求返回值的模板类</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">class BaseResp&lt;T&gt; &#123;</span><br><span class="line">  String status;</span><br><span class="line">  T data;</span><br><span class="line">  String token;</span><br><span class="line">  String text;</span><br><span class="line"></span><br><span class="line">  BaseResp(this.status, this.data, this.token, this.text);</span><br><span class="line"></span><br><span class="line">  bool get success =&gt; status == &quot;1&quot;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在Api类中加入Post方法的封装:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br></pre></td><td class="code"><pre><span class="line">Future&lt;BaseResp&lt;T&gt;&gt; post&lt;T&gt;(</span><br><span class="line">    String path, &#123;</span><br><span class="line">    @required JsonProcessor&lt;T&gt; processor,</span><br><span class="line">    Map&lt;String, dynamic&gt; formData,</span><br><span class="line">    CancelToken cancelToken,</span><br><span class="line">    ProgressCallback onReceiveProgress,</span><br><span class="line">    ProgressCallback onSendProgress,</span><br><span class="line">    useFormData: false,</span><br><span class="line">  &#125;) async &#123;</span><br><span class="line">    assert(processor != null);</span><br><span class="line">    processor = processor ?? (dynamic raw) =&gt; null;</span><br><span class="line">    formData = formData ?? &#123;&#125;;</span><br><span class="line">    cancelToken = cancelToken ?? CancelToken();</span><br><span class="line">    onReceiveProgress = onReceiveProgress ??</span><br><span class="line">        (count, total) &#123;</span><br><span class="line">          ///默认接收进度</span><br><span class="line">        &#125;;</span><br><span class="line">    onSendProgress = onSendProgress ??</span><br><span class="line">        (count, total) &#123;</span><br><span class="line">          ///默认发送进度</span><br><span class="line">        &#125;;</span><br><span class="line">    Response resp = await _dio.post(</span><br><span class="line">      path,</span><br><span class="line">      options: RequestOptions(</span><br><span class="line">        responseType: ResponseType.json,</span><br><span class="line">        headers: &#123;</span><br><span class="line">          AuthorizationHeader: Cache().token,</span><br><span class="line">        &#125;,</span><br><span class="line">        contentType: useFormData</span><br><span class="line">            ? ContentTypeFormDataValue</span><br><span class="line">            : ContentTypeFormUrlEncodeValue,</span><br><span class="line">        data: useFormData ? FormData.fromMap(formData) : formData,</span><br><span class="line">      ),</span><br><span class="line">      cancelToken: cancelToken,</span><br><span class="line">      onSendProgress: onSendProgress,</span><br><span class="line">      onReceiveProgress: onReceiveProgress,</span><br><span class="line">    );</span><br><span class="line">    dynamic map;</span><br><span class="line">    if (resp.headers</span><br><span class="line">        .value(ContentTypeHeader)</span><br><span class="line">        .contains(ContentTypeTextPlainValue)) &#123;</span><br><span class="line">      map = json.decode(resp.data);</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">      map = resp.data;</span><br><span class="line">    &#125;</span><br><span class="line">    dynamic status = map[&quot;status&quot;];</span><br><span class="line">    dynamic text = map[&quot;text&quot;];</span><br><span class="line">    dynamic token = map[&quot;token&quot;];</span><br><span class="line">    dynamic _rawData = map[&quot;data&quot;];</span><br><span class="line">    T data;</span><br><span class="line">    try &#123;</span><br><span class="line">      if (status.toString() == &#x27;1&#x27;) data = processor(_rawData);</span><br><span class="line">    &#125; catch (e, s) &#123;</span><br><span class="line">      print(e);</span><br><span class="line">      print(s);</span><br><span class="line">    &#125;</span><br><span class="line">    return BaseResp&lt;T&gt;(</span><br><span class="line">        status.toString(), data, token.toString(), text.toString());</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  Future&lt;BaseResp&lt;T&gt;&gt; get&lt;T&gt;(</span><br><span class="line">    String path, &#123;</span><br><span class="line">    @required JsonProcessor&lt;T&gt; processor,</span><br><span class="line">    Map&lt;String, dynamic&gt; queryMap,</span><br><span class="line">    CancelToken cancelToken,</span><br><span class="line">    ProgressCallback onReceiveProgress,</span><br><span class="line">  &#125;) async &#123;</span><br><span class="line">    assert(processor != null);</span><br><span class="line">    processor = processor ?? (dynamic raw) =&gt; null;</span><br><span class="line">    queryMap = queryMap ?? &#123;&#125;;</span><br><span class="line">    cancelToken = cancelToken ?? CancelToken();</span><br><span class="line">    onReceiveProgress = onReceiveProgress ??</span><br><span class="line">        (count, total) &#123;</span><br><span class="line">          ///默认接收进度</span><br><span class="line">        &#125;;</span><br><span class="line">    Response resp = await _dio.get(</span><br><span class="line">      path,</span><br><span class="line">      queryParameters: queryMap,</span><br><span class="line">      options: RequestOptions(</span><br><span class="line">          responseType: ResponseType.json,</span><br><span class="line">          headers: &#123;</span><br><span class="line">            AuthorizationHeader: Cache().token,</span><br><span class="line">          &#125;,</span><br><span class="line">          queryParameters: queryMap,</span><br><span class="line">          contentType: ContentTypeFormUrlEncodeValue),</span><br><span class="line">      cancelToken: cancelToken,</span><br><span class="line">      onReceiveProgress: onReceiveProgress,</span><br><span class="line">    );</span><br><span class="line">    dynamic map;</span><br><span class="line">    if (resp.headers</span><br><span class="line">        .value(ContentTypeHeader)</span><br><span class="line">        .contains(ContentTypeTextPlainValue)) &#123;</span><br><span class="line">      map = json.decode(resp.data);</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">      map = resp.data;</span><br><span class="line">    &#125;</span><br><span class="line">    dynamic status = map[&quot;status&quot;];</span><br><span class="line">    dynamic text = map[&quot;text&quot;];</span><br><span class="line">    dynamic token = map[&quot;token&quot;];</span><br><span class="line">    dynamic _rawData = map[&quot;data&quot;];</span><br><span class="line">    T data;</span><br><span class="line">    try &#123;</span><br><span class="line">      if (status.toString() == &#x27;1&#x27;) data = processor(_rawData);</span><br><span class="line">    &#125; catch (e,s) &#123;</span><br><span class="line">      print(e);</span><br><span class="line">      print(s);</span><br><span class="line">    &#125;</span><br><span class="line">    return BaseResp&lt;T&gt;(</span><br><span class="line">        status.toString(), data, token.toString(), text.toString());</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure>

<p>当然还有定义好json数据处理函数</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">typedef JsonProcessor&lt;T&gt; = T Function(dynamic json);</span><br></pre></td></tr></table></figure>


<h3 id="FutureBuilder与网络请求"><a href="#FutureBuilder与网络请求" class="headerlink" title="FutureBuilder与网络请求"></a>FutureBuilder与网络请求</h3><p>FutureBuilder是Flutter Framework中提供的异步构建视图的一个Widget,提供给它一个Future类型的数据,它会执行future任务封装成AsyncSnapshot返回,并在error,success情况下rebuild.我们的网络工具一般都是封装成async方法,也就是返回值为Future的方法,我们可以给网络请求到视图显示这一步加入转场特效还有错误处理/重试功能.</p>
<p>首先我们的FutureBuilder</p>

    </article>
    <!-- license  -->
    
        <div class="license-wrapper">
            <p>原文作者：<a href="https://apm29.github.io">Apm29</a>
            <p>原文链接：<a href="https://apm29.github.io/2019/12/23/%E7%94%A8Flutter%E9%87%8D%E6%9E%84%E5%BA%94%E7%94%A8/">https://apm29.github.io/2019/12/23/%E7%94%A8Flutter%E9%87%8D%E6%9E%84%E5%BA%94%E7%94%A8/</a>
            <p>发表日期：<a href="https://apm29.github.io/2019/12/23/%E7%94%A8Flutter%E9%87%8D%E6%9E%84%E5%BA%94%E7%94%A8/">December 23rd 2019, 2:09:19 pm</a>
            <p>更新日期：<a href="https://apm29.github.io/2019/12/23/%E7%94%A8Flutter%E9%87%8D%E6%9E%84%E5%BA%94%E7%94%A8/">March 25th 2020, 8:38:57 am</a>
            <p>版权声明：本文采用<a rel="license noopener" target="_blank" href="http://creativecommons.org/licenses/by-nc/4.0/">知识共享署名-非商业性使用 4.0 国际许可协议</a>进行许可</p>
        </div>
    
    <!-- paginator  -->
    <ul class="post-paginator">
        <li class="next">
            
                <div class="nextSlogan">Next Post</div>
                <a href= "/2020/01/07/Linux-OSX-%E5%91%BD%E4%BB%A4/" title= "Linux/OSX 命令">
                    <div class="nextTitle">Linux/OSX 命令</div>
                </a>
            
        </li>
        <li class="previous">
            
                <div class="prevSlogan">Previous Post</div>
                <a href= "/2019/12/20/Docker%E7%AC%94%E8%AE%B0/" title= "Docker笔记">
                    <div class="prevTitle">Docker笔记</div>
                </a>
            
        </li>
    </ul>
    <!-- 评论插件 -->
    <!-- 来必力City版安装代码 -->

<!-- City版安装代码已完成 -->
    
    
    <!-- partial('_partial/comment/changyan') -->
    <!--PC版-->


    
    

    <!-- 评论 -->
</main>
            <!-- profile -->
            
        </div>
        <footer class="footer footer-unloaded">
    <!-- social  -->
    
    <div class="social">
        
    
        
            
                <a href="mailto:yjw999wow@gmail.com" class="iconfont-archer email" title=email ></a>
            
        
    
        
            
                <a href="//github.com/apm29" class="iconfont-archer github" target="_blank" title=github></a>
            
        
    
        
    
        
    
        
    
        
    
        
    
        
    
        
    
        
    
        
    
        
    
        
    
        
    
        
    
        
    
        
    
        
    
        
    
        
    

    </div>
    
    <!-- powered by Hexo  -->
    <div class="copyright">
        <span id="hexo-power">Powered by <a href="https://hexo.io/" target="_blank">Hexo</a></span><span class="iconfont-archer power">&#xe635;</span><span id="theme-info">theme <a href="https://github.com/fi3ework/hexo-theme-archer" target="_blank">Archer</a></span>
    </div>
    <div style="text-align: center">
        <img src="">
        <a href="https://beian.miit.gov.cn/"  style="color:#f72b07" target="_blank">浙ICP备20011000号-1</a>
    </div>
    <!-- 不蒜子  -->
    
    <div class="busuanzi-container">
    
    
    <span id="busuanzi_container_site_pv">PV: <span id="busuanzi_value_site_pv"></span> :)</span>
    
    </div>
    
</footer>

    </div>
    <!-- toc -->
    
    <div class="toc-wrapper" style=
    







top:50vh;

    >
        <div class="toc-catalog">
            <span class="iconfont-archer catalog-icon">&#xe613;</span><span>CATALOG</span>
        </div>
        <ol class="toc"><li class="toc-item toc-level-1"><a class="toc-link" href="#%E5%89%8D%E8%A8%80"><span class="toc-number">1.</span> <span class="toc-text">前言</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83"><span class="toc-number">2.</span> <span class="toc-text">开发环境</span></a></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E9%A1%B9%E7%9B%AE%E5%88%9B%E5%BB%BA"><span class="toc-number">3.</span> <span class="toc-text">项目创建</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%91%BD%E4%BB%A4%E8%A1%8C%E5%88%9B%E5%BB%BA%E9%A1%B9%E7%9B%AE"><span class="toc-number">3.1.</span> <span class="toc-text">命令行创建项目</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E8%AE%BE%E7%BD%AE%E5%AD%90%E6%A8%A1%E5%9D%97"><span class="toc-number">3.2.</span> <span class="toc-text">设置子模块</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E9%A1%B9%E7%9B%AE%E6%A1%86%E6%9E%B6%E6%90%AD%E5%BB%BA"><span class="toc-number">4.</span> <span class="toc-text">项目框架搭建</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86"><span class="toc-number">4.1.</span> <span class="toc-text">状态管理</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#flutter-redux-%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5"><span class="toc-number">4.1.1.</span> <span class="toc-text">flutter_redux 基本概念</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#redux%E7%9A%84Store"><span class="toc-number">4.1.2.</span> <span class="toc-text">redux的Store</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#StoreProvider"><span class="toc-number">4.1.3.</span> <span class="toc-text">StoreProvider</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#StoreConnector"><span class="toc-number">4.1.4.</span> <span class="toc-text">StoreConnector</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#StoreBuilder"><span class="toc-number">4.1.5.</span> <span class="toc-text">StoreBuilder</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Redux%E5%8C%85%E4%B8%8B%E6%8F%90%E4%BE%9B%E7%9A%84%E5%B7%A5%E5%85%B7%E6%96%B9%E6%B3%95"><span class="toc-number">4.1.6.</span> <span class="toc-text">Redux包下提供的工具方法</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E8%B7%AF%E7%94%B1%E7%AE%A1%E7%90%86"><span class="toc-number">4.2.</span> <span class="toc-text">路由管理</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%BC%95%E5%85%A5"><span class="toc-number">4.2.1.</span> <span class="toc-text">引入</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8"><span class="toc-number">4.2.2.</span> <span class="toc-text">使用</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E5%9B%BD%E9%99%85%E5%8C%96"><span class="toc-number">4.3.</span> <span class="toc-text">国际化</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%AF%BC%E5%85%A5flutter-localization%E5%8C%85"><span class="toc-number">4.3.1.</span> <span class="toc-text">导入flutter_localization包</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%BC%95%E5%85%A5-1"><span class="toc-number">4.3.2.</span> <span class="toc-text">引入</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8Flutter-i18n%E6%8F%92%E4%BB%B6"><span class="toc-number">4.3.3.</span> <span class="toc-text">使用Flutter-i18n插件</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%A3%B0%E6%98%8E%E8%B5%84%E6%BA%90"><span class="toc-number">4.3.4.</span> <span class="toc-text">声明资源</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8%E8%B5%84%E6%BA%90"><span class="toc-number">4.3.5.</span> <span class="toc-text">使用资源</span></a></li></ol></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#%E6%8F%92%E4%BB%B6%E7%9A%84%E4%BD%BF%E7%94%A8"><span class="toc-number">5.</span> <span class="toc-text">插件的使用</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#camera-0-5-7-2"><span class="toc-number">5.1.</span> <span class="toc-text">camera 0.5.7+2</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%BC%95%E5%85%A5-2"><span class="toc-number">5.1.1.</span> <span class="toc-text">引入</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#iOS%E6%9D%83%E9%99%90"><span class="toc-number">5.1.2.</span> <span class="toc-text">iOS权限</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E8%8E%B7%E5%8F%96Camera"><span class="toc-number">5.1.3.</span> <span class="toc-text">获取Camera</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E4%BD%BF%E7%94%A8-1"><span class="toc-number">5.1.4.</span> <span class="toc-text">使用</span></a></li></ol></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#Widget%E4%BD%BF%E7%94%A8"><span class="toc-number">6.</span> <span class="toc-text">Widget使用</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#ClipPath"><span class="toc-number">6.1.</span> <span class="toc-text">ClipPath</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#%E7%AE%80%E5%8D%95%E5%B0%81%E8%A3%85"><span class="toc-number">6.2.</span> <span class="toc-text">简单封装</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Dio%E7%AE%80%E5%8D%95%E5%B0%81%E8%A3%85"><span class="toc-number">6.2.1.</span> <span class="toc-text">Dio简单封装</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#FutureBuilder%E4%B8%8E%E7%BD%91%E7%BB%9C%E8%AF%B7%E6%B1%82"><span class="toc-number">6.2.2.</span> <span class="toc-text">FutureBuilder与网络请求</span></a></li></ol></li></ol></li></ol>
    </div>
    
    <div class="back-top iconfont-archer">&#xe639;</div>
    <div class="sidebar sidebar-hide">
    <ul class="sidebar-tabs sidebar-tabs-active-0">
        <li class="sidebar-tab-archives"><span class="iconfont-archer">&#xe67d;</span><span class="tab-name">Archive</span></li>
        <li class="sidebar-tab-tags"><span class="iconfont-archer">&#xe61b;</span><span class="tab-name">Tag</span></li>
        <li class="sidebar-tab-categories"><span class="iconfont-archer">&#xe666;</span><span class="tab-name">Cate</span></li>
    </ul>
    <div class="sidebar-content sidebar-content-show-archive">
          <div class="sidebar-panel-archives">
    <!-- 在ejs中将archive按照时间排序 -->
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    <div class="total-and-search">
        <div class="total-archive">
        Total : 44
        </div>
        <!-- search  -->
        
    </div>
    
    <div class="post-archive">
    
    
    
    
    <div class="archive-year"> 2022 </div>
    <ul class="year-list">
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">07/06</span><a class="archive-post-title" href= "/2022/07/06/%E4%BD%BF%E7%94%A8KeepAlive%E5%8A%A8%E6%80%81%E7%BC%93%E5%AD%98%E6%A0%87%E7%AD%BE%E9%A1%B5/" >使用KeepAlive动态缓存标签页</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">07/05</span><a class="archive-post-title" href= "/2022/07/05/SheetJS%20--%20%E5%89%8D%E7%AB%AF(%E6%B5%8F%E8%A7%88%E5%99%A8)%E5%AF%BC%E5%87%BAExcel%E6%96%87%E4%BB%B6/" >SheetJS -- 前端(浏览器)导出Excel文件</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">06/22</span><a class="archive-post-title" href= "/2022/06/22/%E9%92%89%E9%92%89H5%E5%BA%94%E7%94%A8%E8%B8%A9%E5%9D%91(1)/" >钉钉H5应用踩坑(1)</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">06/20</span><a class="archive-post-title" href= "/2022/06/20/%E4%BD%BF%E7%94%A8-vue-composition-api/" >使用@vue/composition-api</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">06/06</span><a class="archive-post-title" href= "/2022/06/06/Python%E6%B8%85%E9%99%A4NodeModules%E6%96%87%E4%BB%B6%E5%A4%B9%E8%84%9A%E6%9C%AC/" >Python清除NodeModules文件夹脚本</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">06/06</span><a class="archive-post-title" href= "/2022/06/06/%E5%AF%B9%E6%9C%80%E8%BF%91%E5%86%99%E7%9A%84Android%E5%BA%94%E7%94%A8%E7%9A%84%E6%80%BB%E7%BB%93(1)/" >对最近写的Android应用的总结(1)</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">05/31</span><a class="archive-post-title" href= "/2022/05/31/Vite-Plugin-Pages%E4%B8%8EKeep-Alive/" >Vite-Plugin-Pages与Keep-Alive</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">05/27</span><a class="archive-post-title" href= "/2022/05/27/VSCode%E9%85%8D%E7%BD%AE%E4%B8%8E%E6%8F%92%E4%BB%B6(Vue%E5%BC%80%E5%8F%91)/" >VSCode配置与插件（Vue开发）</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">01/19</span><a class="archive-post-title" href= "/2022/01/19/%E9%9D%99%E6%80%81%E7%BD%91%E7%AB%99%E7%94%9F%E6%88%90%E5%99%A8-Gatsby-%E5%AD%A6%E4%B9%A0/" >静态网站生成器 Gatsby 学习</a>
        </li>
    
    
    
    
    
        </ul>
    
    <div class="archive-year"> 2021 </div>
    <ul class="year-list">
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">12/21</span><a class="archive-post-title" href= "/2021/12/21/Kotlin%E5%8D%8F%E7%A8%8B%E7%9B%B8%E5%85%B3/" >Kotlin协程相关</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">08/09</span><a class="archive-post-title" href= "/2021/08/09/%E5%88%9D%E8%AF%86Ktorm/" >初识Ktorm</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">08/05</span><a class="archive-post-title" href= "/2021/08/05/NUXTJS%E7%9A%84%E5%A4%9A%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE/" >NUXTJS的多环境配置</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">08/05</span><a class="archive-post-title" href= "/2021/08/05/NuxtJS%E9%9D%9E%E6%A0%B9%E7%9B%AE%E5%BD%95%E4%B8%8B%E9%83%A8%E7%BD%B2/" >NuxtJS非根目录下部署</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">07/24</span><a class="archive-post-title" href= "/2021/07/24/%E4%BD%BF%E7%94%A8vue-sfc-rollup%E5%88%9B%E5%BB%BA%E8%87%AA%E5%B7%B1%E7%9A%84NPM%E5%BA%93/" >使用vue-sfc-rollup创建自己的NPM库</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">07/19</span><a class="archive-post-title" href= "/2021/07/19/Certbot%E7%94%9F%E6%88%90Https%E8%AF%81%E4%B9%A6/" >Certbot生成Https证书</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">07/19</span><a class="archive-post-title" href= "/2021/07/19/Ubuntu%E4%B8%ADMysql5-0%E9%87%8D%E6%96%B0%E5%AE%89%E8%A3%85%E4%B8%8E%E5%88%9D%E5%A7%8B%E5%8C%96/" >Ubuntu中Mysql5.0重新安装与初始化</a>
        </li>
    
    
    
    
    
        </ul>
    
    <div class="archive-year"> 2020 </div>
    <ul class="year-list">
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">10/28</span><a class="archive-post-title" href= "/2020/10/28/JetPack-Navigation%E4%B8%ADFragment%E7%9A%84%E8%BF%94%E5%9B%9E%E9%94%AE%E6%8B%A6%E6%88%AA/" >JetPack-Navigation中Fragment的返回键拦截</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">07/07</span><a class="archive-post-title" href= "/2020/07/07/Flutter%E5%9B%BD%E9%99%85%E5%8C%96flutter-intl/" >Flutter国际化flutter_intl</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">06/18</span><a class="archive-post-title" href= "/2020/06/18/Vue%E9%A1%B9%E7%9B%AE%E5%8A%A8%E6%80%81%E8%B7%AF%E7%94%B1%E5%AE%9E%E8%B7%B5%EF%BC%88%E5%AE%9E%E7%8E%B0%E5%8A%A8%E6%80%81%E6%9D%83%E9%99%90%EF%BC%89/" >Vue项目动态路由实践（实现动态权限）</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">05/29</span><a class="archive-post-title" href= "/2020/05/29/%E7%94%A8%E5%88%86%E6%B2%BB%E6%80%9D%E6%83%B3%E9%9D%9E%E9%80%92%E5%BD%92%E8%8E%B7%E5%8F%96N%E4%B8%AA%E6%95%B0%E7%BB%84%E5%85%83%E7%B4%A0%E7%9A%84%E7%BB%84%E5%90%88/" >用分治思想非递归获取N个数组元素的组合</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">03/23</span><a class="archive-post-title" href= "/2020/03/23/TravisCi-Gradle%E9%83%A8%E7%BD%B2SpringBoot%E9%A1%B9%E7%9B%AE%E5%88%B0aliyun-ECS/" >TravisCi/Gradle部署SpringBoot项目到aliyun ECS</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">01/07</span><a class="archive-post-title" href= "/2020/01/07/Linux-OSX-%E5%91%BD%E4%BB%A4/" >Linux/OSX 命令</a>
        </li>
    
    
    
    
    
        </ul>
    
    <div class="archive-year"> 2019 </div>
    <ul class="year-list">
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">12/23</span><a class="archive-post-title" href= "/2019/12/23/%E7%94%A8Flutter%E9%87%8D%E6%9E%84%E5%BA%94%E7%94%A8/" >用Flutter重构应用</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">12/20</span><a class="archive-post-title" href= "/2019/12/20/Docker%E7%AC%94%E8%AE%B0/" >Docker笔记</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">12/19</span><a class="archive-post-title" href= "/2019/12/19/Hexo/" >Hexo</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">12/19</span><a class="archive-post-title" href= "/2019/12/19/Android-Text-measure-draw/" >Android Text measure/draw</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">12/19</span><a class="archive-post-title" href= "/2019/12/19/Flutter-WebView-%E9%94%AE%E7%9B%98%E9%97%AE%E9%A2%98/" >Flutter WebView 键盘问题</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">12/19</span><a class="archive-post-title" href= "/2019/12/19/Git-Submodule-%E5%AD%90%E6%A8%A1%E5%9D%97/" >Git Submodule(子模块)</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">12/18</span><a class="archive-post-title" href= "/2019/12/18/Android-Webview%E8%AE%BE%E7%BD%AECookie/" >Android Webview设置Cookie</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">12/18</span><a class="archive-post-title" href= "/2019/12/18/NPM%E8%AE%BE%E7%BD%AERegistry/" >NPM设置Registry</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">12/17</span><a class="archive-post-title" href= "/2019/12/17/LiveData/" >LiveData</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">12/13</span><a class="archive-post-title" href= "/2019/12/13/AndroidDevMetrics/" >AndroidDevMetrics</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">12/11</span><a class="archive-post-title" href= "/2019/12/11/CustomPainter/" >CustomPaint</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">12/11</span><a class="archive-post-title" href= "/2019/12/11/Dart%E5%AD%A6%E4%B9%A0/" >Dart 语法</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">12/11</span><a class="archive-post-title" href= "/2019/12/11/Databinding%20in%20kotlin/" >DataBinding in kotlin</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">12/11</span><a class="archive-post-title" href= "/2019/12/11/Gradle%E5%AD%A6%E4%B9%A0/" >Gradle与Groovy</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">12/11</span><a class="archive-post-title" href= "/2019/12/11/JetpackSetup/" >Jetpack</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">12/11</span><a class="archive-post-title" href= "/2019/12/11/PDFViewerInAndroid/" >PDFViewer on Android</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">12/11</span><a class="archive-post-title" href= "/2019/12/11/Navigation/" >Navigation初探</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">12/11</span><a class="archive-post-title" href= "/2019/12/11/RxCache/" >RxCache</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">12/11</span><a class="archive-post-title" href= "/2019/12/11/RxLifecycle%E5%AD%A6%E4%B9%A0/" >RxLifecycle</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">12/11</span><a class="archive-post-title" href= "/2019/12/11/Spring/" >SpringFrameWork</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">12/11</span><a class="archive-post-title" href= "/2019/12/11/TextView%E4%B8%AD%E6%98%BE%E7%A4%BAHtml%E5%86%85%E5%AE%B9/" >在TextView中显示Html格式的文字</a>
        </li>
    
    
        <li class="archive-post-item">
            <span class="archive-post-date">12/11</span><a class="archive-post-title" href= "/2019/12/11/flutter/" >Flutter</a>
        </li>
    
    </div>
  </div>
        <div class="sidebar-panel-tags">
    <div class="sidebar-tags-name">
    
        <span class="sidebar-tag-name" data-tags="Android"><span class="iconfont-archer">&#xe606;</span>Android</span>
    
        <span class="sidebar-tag-name" data-tags="JetPack"><span class="iconfont-archer">&#xe606;</span>JetPack</span>
    
        <span class="sidebar-tag-name" data-tags="Tools"><span class="iconfont-archer">&#xe606;</span>Tools</span>
    
        <span class="sidebar-tag-name" data-tags="Linux"><span class="iconfont-archer">&#xe606;</span>Linux</span>
    
        <span class="sidebar-tag-name" data-tags="Flutter"><span class="iconfont-archer">&#xe606;</span>Flutter</span>
    
        <span class="sidebar-tag-name" data-tags="Dart"><span class="iconfont-archer">&#xe606;</span>Dart</span>
    
        <span class="sidebar-tag-name" data-tags="Kotlin"><span class="iconfont-archer">&#xe606;</span>Kotlin</span>
    
        <span class="sidebar-tag-name" data-tags="DataBinding"><span class="iconfont-archer">&#xe606;</span>DataBinding</span>
    
        <span class="sidebar-tag-name" data-tags="Docker"><span class="iconfont-archer">&#xe606;</span>Docker</span>
    
        <span class="sidebar-tag-name" data-tags="Git"><span class="iconfont-archer">&#xe606;</span>Git</span>
    
        <span class="sidebar-tag-name" data-tags="Gradle"><span class="iconfont-archer">&#xe606;</span>Gradle</span>
    
        <span class="sidebar-tag-name" data-tags="Groovy"><span class="iconfont-archer">&#xe606;</span>Groovy</span>
    
        <span class="sidebar-tag-name" data-tags="NPM"><span class="iconfont-archer">&#xe606;</span>NPM</span>
    
        <span class="sidebar-tag-name" data-tags="travisci"><span class="iconfont-archer">&#xe606;</span>travisci</span>
    
        <span class="sidebar-tag-name" data-tags="Fragment"><span class="iconfont-archer">&#xe606;</span>Fragment</span>
    
        <span class="sidebar-tag-name" data-tags="Coroutines"><span class="iconfont-archer">&#xe606;</span>Coroutines</span>
    
        <span class="sidebar-tag-name" data-tags="Shell"><span class="iconfont-archer">&#xe606;</span>Shell</span>
    
        <span class="sidebar-tag-name" data-tags="前端"><span class="iconfont-archer">&#xe606;</span>前端</span>
    
        <span class="sidebar-tag-name" data-tags="nuxtjs"><span class="iconfont-archer">&#xe606;</span>nuxtjs</span>
    
        <span class="sidebar-tag-name" data-tags="Python"><span class="iconfont-archer">&#xe606;</span>Python</span>
    
        <span class="sidebar-tag-name" data-tags="SheetJS"><span class="iconfont-archer">&#xe606;</span>SheetJS</span>
    
        <span class="sidebar-tag-name" data-tags="Spring"><span class="iconfont-archer">&#xe606;</span>Spring</span>
    
        <span class="sidebar-tag-name" data-tags="Springboot"><span class="iconfont-archer">&#xe606;</span>Springboot</span>
    
        <span class="sidebar-tag-name" data-tags="TravisCI"><span class="iconfont-archer">&#xe606;</span>TravisCI</span>
    
        <span class="sidebar-tag-name" data-tags="MySQL"><span class="iconfont-archer">&#xe606;</span>MySQL</span>
    
        <span class="sidebar-tag-name" data-tags="Vue"><span class="iconfont-archer">&#xe606;</span>Vue</span>
    
        <span class="sidebar-tag-name" data-tags="Vite"><span class="iconfont-archer">&#xe606;</span>Vite</span>
    
        <span class="sidebar-tag-name" data-tags="ORM"><span class="iconfont-archer">&#xe606;</span>ORM</span>
    
        <span class="sidebar-tag-name" data-tags="算法"><span class="iconfont-archer">&#xe606;</span>算法</span>
    
        <span class="sidebar-tag-name" data-tags="javascript"><span class="iconfont-archer">&#xe606;</span>javascript</span>
    
        <span class="sidebar-tag-name" data-tags="SSG"><span class="iconfont-archer">&#xe606;</span>SSG</span>
    
        <span class="sidebar-tag-name" data-tags="SEO"><span class="iconfont-archer">&#xe606;</span>SEO</span>
    
    </div>
    <div class="iconfont-archer sidebar-tags-empty">&#xe678;</div>
    <div class="tag-load-fail" style="display: none; color: #ccc; font-size: 0.6rem;">
    缺失模块。<br/>
    1、请确保node版本大于6.2<br/>
    2、在博客根目录（注意不是archer根目录）执行以下命令：<br/>
    <span style="color: #f75357; font-size: 1rem; line-height: 2rem;">npm i hexo-generator-json-content --save</span><br/>
    3、在根目录_config.yml里添加配置：
    <pre style="color: #787878; font-size: 0.6rem;">
jsonContent:
  meta: false
  pages: false
  posts:
    title: true
    date: true
    path: true
    text: false
    raw: false
    content: false
    slug: false
    updated: false
    comments: false
    link: false
    permalink: false
    excerpt: false
    categories: true
    tags: true</pre>
    </div> 
    <div class="sidebar-tags-list"></div>
</div>
        <div class="sidebar-panel-categories">
    <div class="sidebar-categories-name">
    
        <span class="sidebar-category-name" data-categories="笔记"><span class="iconfont-archer">&#xe60a;</span>笔记</span>
    
        <span class="sidebar-category-name" data-categories="Git"><span class="iconfont-archer">&#xe60a;</span>Git</span>
    
    </div>
    <div class="iconfont-archer sidebar-categories-empty">&#xe678;</div>
    <div class="sidebar-categories-list"></div>
</div>
    </div>
</div> 
    <script>
    var siteMeta = {
        root: "/",
        author: "Apm29"
    }
</script>
    <!-- CDN failover -->
    <script src="https://unpkg.com/jquery@3.3.1/dist/jquery.min.js"></script>
    <script type="text/javascript">
        if (typeof window.$ === 'undefined')
        {
            console.warn('jquery load from jsdelivr failed, will load local script')
            document.write('<script src="/lib/jquery.min.js">\x3C/script>')
        }
    </script>
    <script src="/scripts/main.js"></script>
    <!-- algolia -->
    
    <!-- busuanzi  -->
    
    <script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
    
    <!-- CNZZ  -->
    
    </div>
    <!-- async load share.js -->
    
        <script src="/scripts/share.js" async></script>    
     
    </body>
</html>


